#include "StdAfx.h"
#include "ImageKeeper.h"
#include "Fourier.h"
#include "CircleDivider.h"
#include "wincodec.h"
#include <d2d1.h>
#include <vector>
#include <algorithm>
#include <assert.h>

extern const int kWmStartUpdatingAnalytics;
extern const int kWmStartUpdatingCircle;

#ifdef min
	#undef min
#endif

template <typename T>
inline void SafeRelease(T *&p)
{
	if (p)
	{
		p->Release();
		p = nullptr;
	}
}

// --- auxiliary structures

struct MagnifierData
{
	COLORREF buffer[kMaxBitmapSide * kMaxBitmapSide];
	int side;
	//
	MagnifierData() : side(0) {};
};

struct AnalyticsData
{
	COLORREF bitmapData[kSideOfMagnifierSquare * kSideOfMagnifierSquare];
	Polynomial polynomial;

	// allocators are needed for aligning DirectMath objects inside
	void *__cdecl operator new(size_t count){ return _aligned_malloc(count, 16); };
	void __cdecl operator delete(void* p) { _aligned_free(p); };
};

struct CircleData
{
	COLORREF smoothData[kSideOfMagnifierSquare * kSideOfMagnifierSquare];
	COLORREF smoothStripeLine[kStripeInterval2pi];

	COLORREF discreteData[kSideOfMagnifierSquare * kSideOfMagnifierSquare];
	COLORREF discreteFewData[kSideOfMagnifierSquare * kSideOfMagnifierSquare];
	Fourier fourier;
	CircleDivider circleDivider;
	CircleDivider circleDividerFew;

	// allocators are needed for DirectMath objects inside
	void *__cdecl operator new(size_t count){ return _aligned_malloc(count, 16); };
	void __cdecl operator delete(void* p) { _aligned_free(p); };
};

//-------------------------------------------------------------------------
// some methods of ImageKeeper
//-------------------------------------------------------------------------

void ImageKeeper::StartUpdatingMagnifierBitmapData()
{
	if (mFormatConverter)
	{
		if (mMagnifierBitmapUpdater.IsEmpty())
		{
			mMagnifierBitmapUpdater.SetNotifierCallback(std::bind(&ImageKeeper::MagnifierDataDidUpdate, this));
			mMagnifierBitmapUpdater.StartRunning(new MagnifierData, new MagnifierData,
				[](void* obj) { delete reinterpret_cast<MagnifierData*>(obj); });

			mAnalyticsUpdater.SetNotifierCallback(std::bind(&ImageKeeper::AnalyticsDidUpdate, this));
			mAnalyticsUpdater.StartRunning(new AnalyticsData, new AnalyticsData,
				[](void* obj) { delete reinterpret_cast<AnalyticsData*>(obj); });

			mCircleUpdater.SetNotifierCallback(std::bind(&ImageKeeper::CircleDataDidUpdate, this));
			mCircleUpdater.StartRunning(new CircleData, new CircleData,
				[](void* obj) { delete reinterpret_cast<CircleData*>(obj); });
		}
		else
		{
			mMagnifierBitmapUpdater.Invalidate();
			mAnalyticsUpdater.Invalidate();
			mIsAnalyticsBitmapValid = false;
			mCircleUpdater.Invalidate();
			mIsCircleBitmapValid = false;
		}

		mFormatConverter->AddRef();
		auto formatConverter = std::shared_ptr<IWICFormatConverter>(mFormatConverter,
			[](IWICFormatConverter *obj) { obj->Release();  });

		using namespace std::placeholders;
		FunctionWithObject func = std::bind(&ImageKeeper::DoUpdateMagnifierBitmapData,
			this, formatConverter, mPreviousCentralPixel, _1);
		mMagnifierBitmapUpdater.UpdateFreeObject(func);
	}
}

void ImageKeeper::DoUpdateMagnifierBitmapData(std::shared_ptr<IWICFormatConverter> inFormatConverter,
	POINT inCenter, void* inObjectToUpdate)
{
	const auto& updater = mMagnifierBitmapUpdater;
	if (updater.IsCancelled())
	{
		return;
	}

	// make sure that buffer is large enough for any value (selected from menu)
	// of kMaxBitmapSide
	assert(mPixelCountPerMagnifierSide <= kMaxBitmapSide);


	auto magnifierData = reinterpret_cast<MagnifierData *>(inObjectToUpdate);

	magnifierData->side = mPixelCountPerMagnifierSide;

	SIZE originalImageSize = GetSize();
	int halfSide = mPixelCountPerMagnifierSide / 2;
	WICRect idealRect = { inCenter.x - halfSide, inCenter.y - halfSide,
		mPixelCountPerMagnifierSide, mPixelCountPerMagnifierSide };
	WICRect rect;
	rect.X = max(0, idealRect.X);
	rect.Y = max(0, idealRect.Y);
	rect.Width = std::min<INT>({ idealRect.Width,
		originalImageSize.cx - idealRect.X, idealRect.X + idealRect.Width });
	rect.Height = std::min<INT>({ idealRect.Height,
		originalImageSize.cy - idealRect.Y, idealRect.Y + idealRect.Height });

	if (updater.IsCancelled())
	{
		return;
	}

	int stride = mPixelCountPerMagnifierSide * sizeof(COLORREF);
	int bufferSize = mPixelCountPerMagnifierSide * stride;

	auto firstPixelPtr = &magnifierData->buffer[0];
	if (0 != ::memcmp(&rect, &idealRect, sizeof(rect)))
	{
		// white color
		::memset(firstPixelPtr, 0xff, bufferSize);

		firstPixelPtr += (rect.X - idealRect.X) +
			mPixelCountPerMagnifierSide * (rect.Y - idealRect.Y);
	}

	if (!updater.IsCancelled() && 0 < rect.Width && 0 < rect.Height)
	{
		inFormatConverter->CopyPixels(&rect, stride, bufferSize, (BYTE*)firstPixelPtr);
	}
}

void ImageKeeper::MagnifierDataDidUpdate()
{
	mMagnifierBitmapNeedsUpdate = true;
	::InvalidateRect(mMagnifierWindow, NULL, false);

	// continue calculations from the main thread
	WPARAM wParam = mMagnifierBitmapUpdater.GetTotalUpdateCount();
	::PostMessage(mMagnifierWindow, kWmStartUpdatingAnalytics, wParam, 0);
}


// ----------------------

void ImageKeeper::StartUpdatingAnalytics(int inCorrespondingUpdateCountForBitmap)
{
	auto copyOfData = std::shared_ptr<MagnifierData>();

	int count = mMagnifierBitmapUpdater.GetTotalUpdateCount();
	if (0 == ((inCorrespondingUpdateCountForBitmap ^ count) & 0xfff))
	{
		if (auto data = mMagnifierBitmapUpdater.BeginRead())
		{
			copyOfData.reset(new MagnifierData(
				*reinterpret_cast<const MagnifierData *>(data)));
			mMagnifierBitmapUpdater.EndRead(data);
		}
	}
	if (copyOfData)
	{
		using namespace std::placeholders;
		FunctionWithObject func = std::bind(&ImageKeeper::DoUpdateAnalytics,
			this, copyOfData, mDegree, _1);
		mAnalyticsUpdater.UpdateFreeObject(func);
	}
}

void ImageKeeper::DoUpdateAnalytics(std::shared_ptr<MagnifierData> inData,
	int inDegree, void* ioObjectToUpdate)
{
	const auto& updater = mAnalyticsUpdater;
	if (updater.IsCancelled())
	{
		return;
	}
	
	LARGE_INTEGER tt1;
	QueryPerformanceCounter(&tt1);

	int side = inData->side;
	mPolynomialCalculator.SetDegreeAndSide(inDegree, side);
	if (updater.IsCancelled())
	{
		return;
	}
	auto analyticsData = reinterpret_cast<AnalyticsData *>(ioObjectToUpdate);
	auto& polynomial = analyticsData->polynomial;
	mPolynomialCalculator.ClearPolynomial(polynomial);
	if (updater.IsCancelled())
	{
		return;
	}

	const COLORREF* buffer = inData->buffer;
	for (int x = 0; x < side; ++x)
	{
		for (int y = 0; y < side; ++y)
		{
			COLORREF color = buffer[x + y * side];
			mPolynomialCalculator.UpdateThePolynomialWithPixel(polynomial, x, y, color);
		}
		if (updater.IsCancelled())
		{
			return;
		}
	}

	LARGE_INTEGER tt2;
	QueryPerformanceCounter(&tt2);
	LONGLONG polynomialCalculationTime = tt2.QuadPart - tt1.QuadPart;
	mLastPolynomialCalculationTime = polynomialCalculationTime;
	mCumulativePolynomialCalculationTime += polynomialCalculationTime;
	++mNumberOfSimilarPolynomialCalculations;


	// --------------------

	if (mDegreeForWhichBaseFunctionsWereCalculated != inDegree)
	{
		float scale = 2.0f / kSideOfMagnifierSquare;
		for (int i = 0; i < kSideOfMagnifierSquare; ++i)
		{
			float x = -1 + scale * i;
			polynomial.GetValuesOfBaseFunctions(x, kMaxDegree + 1,
				mBufferForBaseFunctions[i]);
		}
		mDegreeForWhichBaseFunctionsWereCalculated = inDegree;
	}

	if (updater.IsCancelled())
	{
		return;
	}
	for (int x = 0; x < kSideOfMagnifierSquare; ++x)
	{
		for (int y = 0; y < kSideOfMagnifierSquare; ++y)
		{
			COLORREF colorRef = polynomial.CalculateColor(kMaxDegree + 1,
				mBufferForBaseFunctions[x], mBufferForBaseFunctions[y]);

			analyticsData->bitmapData[x + kSideOfMagnifierSquare * y] = (colorRef | 0xff000000);
		}
		if (updater.IsCancelled())
		{
			return;
		}
	}

	
	auto deltaT = tt2.QuadPart - tt1.QuadPart;
	LARGE_INTEGER freq;
	::QueryPerformanceFrequency(&freq);
	double dt = 1000.0 * double(tt2.QuadPart - tt1.QuadPart) / double(freq.QuadPart);

	char buf[100];
	sprintf_s(buf, "\n analytics time = %lf ms", dt);
	OutputDebugStringA(buf);
}

void ImageKeeper::AnalyticsDidUpdate()
{
	mAnalyticsNeedsUpdate = true;
	if (mShowAnalytic)
	{
		::InvalidateRect(mMagnifierWindow, NULL, false);
	}

	// continue calculations from the main thread
	::PostMessage(mMagnifierWindow, kWmStartUpdatingCircle, 0, 0);
}


void ImageKeeper::StartUpdatingCircle()
{
	mCircleNeedsUpdate = false;
	mIsCircleBitmapValid = false;

	auto polynomialCopy = std::shared_ptr<Polynomial>();
	if (auto dataPtr = static_cast<const AnalyticsData*>(mAnalyticsUpdater.BeginRead()))
	{
		polynomialCopy.reset(new Polynomial(dataPtr->polynomial));
		mAnalyticsUpdater.EndRead(dataPtr);
	}

	if (polynomialCopy)
	{
		using namespace std::placeholders;
		FunctionWithObject func = std::bind(&ImageKeeper::DoUpdateCircle,
			this, polynomialCopy, mCenter, mCircleRadius, _1);
		mCircleUpdater.UpdateFreeObject(func);
	}
}

void ImageKeeper::DoUpdateCircle(std::shared_ptr<Polynomial> inPolynomial,
	Pointf inCenter, float inRadius, void* ioObjectToUpdate)
{
	const auto& updater = mCircleUpdater;
	if (updater.IsCancelled())
	{
		return;
	}
	auto circleData = reinterpret_cast<CircleData *>(ioObjectToUpdate);
	auto& fourier = circleData->fourier;

	LARGE_INTEGER startTime;
	::QueryPerformanceCounter(&startTime);

	// convert coordinates to square x, y = (-1 ... +1)
	float scale = 2.0f / kSideOfMagnifierSquare;
	float convertedCenterX = float(-1.0f + (scale * inCenter.x));
	float convertedCenterY = float(-1.0f + (scale * inCenter.y));
	float convertedRadius = scale * inRadius;

	mFourierCalculator.SetDegree(inPolynomial->GetDegree());
	mFourierCalculator.BeginUpdatingFourier(fourier);
	float dx, dy;
	while (mFourierCalculator.GetNextPoint(dx, dy))
	{
		// flip "dy" in order to see the angle direction as in mathematics
		float x = convertedCenterX + dx * convertedRadius;
		float y = convertedCenterY - dy * convertedRadius;
		auto color = inPolynomial->CalculateColorComponents(x, y);
		mFourierCalculator.AddColorToFourier(color, fourier);
	}
	mFourierCalculator.EndUpdatingFourier(fourier);

	int iterationCount = 0;
	circleData->circleDivider.ResetSegments(fourier);
	circleData->circleDivider.Adjust(fourier, iterationCount);
	mMaxIterationsForCircleDivision = max(mMaxIterationsForCircleDivision,
		iterationCount);
	mCumulativeIterationsForCircleDivision += iterationCount;
	++mNumberOfCircleDivisions;

	circleData->circleDividerFew = circleData->circleDivider;
	circleData->circleDividerFew.MergeSegments(fourier);
	circleData->circleDividerFew.Adjust(fourier, iterationCount);
	mMaxIterationsForCircleDivision = max(mMaxIterationsForCircleDivision,
		iterationCount);

	LARGE_INTEGER circleCalculationFinishTime;
	::QueryPerformanceCounter(&circleCalculationFinishTime);
	LONGLONG t = circleCalculationFinishTime.QuadPart - startTime.QuadPart;
	mLastCircleCalculationTime = t;
	mCumulativeCircleCalculationTime += t;
	++mNumberOfSimilarCircleCalculations;

	// ---------------------------
	if (updater.IsCancelled())
	{
		return;
	}

	ZeroMemory(circleData->smoothData, sizeof(circleData->smoothData));

	float r1 = inRadius - 10.0f;
	float r2 = inRadius + 10.0f;
	float q1 = r1 * r1;
	float q2 = r2 * r2;
	int yMax = std::min<int>(lround(inCenter.y + r2), kSideOfMagnifierSquare);
	for (int y = max(0, lround(inCenter.y - r2)); y < yMax; ++y)
	{
		float dy = float(y - inCenter.y);
		float Qy = dy * dy;
		if (q2 <= Qy)
		{
			continue;
		}

		float minQx = q1 - Qy;
		float rx = sqrt(q2 - Qy);

		int xMin = max(0, lround(inCenter.x - rx));
		int xMax = std::min<int>(lround(inCenter.x + rx), kSideOfMagnifierSquare);
		int xInnerMin = xMax;
		int xInnerMax = xMax;
		if (q1 > Qy)
		{
			float rx1 = sqrt(q1 - Qy);
			xInnerMin = max(0, lround(inCenter.x - rx1));
			xInnerMax = lround(inCenter.x + rx1);
			if (xInnerMin >= xInnerMax)
			{
				// make this value unreachable in the cycle below
				xInnerMin = xMax;
			}
		}

		for (int x = xMin; x < xMax; ++x)
		{
			if (x == xInnerMin)
			{
				x = xInnerMax - 1;
				continue;
			}
			float dx = float(x - inCenter.x);
			// flip "dy" once again for correct mapping
			COLORREF color = fourier.ColorAtAngle(atan2(-dy, dx));
			circleData->smoothData[x + kSideOfMagnifierSquare * y] =
				(color | 0xff000000);
		}

		if (updater.IsCancelled())
		{
			return;
		}
	}

	static const float scaleToRadians = float(2 * M_PI / kStripeInterval2pi);
	for (int i = kStripeInterval2pi; i--;)
	{
		COLORREF color = fourier.ColorAtAngle(i * scaleToRadians);
		circleData->smoothStripeLine[i] = (color | 0xff000000);
	}
}

void ImageKeeper::CircleDataDidUpdate()
{
	mCircleNeedsUpdate = true;
	::InvalidateRect(mMagnifierWindow, nullptr, false);
}

///////////////////////////////////////////////////////////////////////////////

void ImageKeeper::UpdateMagnifierBitmap()
{
	if (nullptr != mMagnifierBitmap &&
		mMagnifierBitmap->GetPixelSize().width != mPixelCountPerMagnifierSide)
	{
		SafeRelease(mMagnifierBitmap);
	}
	bool needUpdate = mMagnifierBitmapNeedsUpdate;

	if (nullptr == mMagnifierBitmap && nullptr != mOriginalBitmap)
	{
		D2D1_SIZE_U size = { mPixelCountPerMagnifierSide, mPixelCountPerMagnifierSide };
		D2D1_BITMAP_PROPERTIES properties = { mOriginalBitmap->GetPixelFormat(), 0, 0 };
		mMagnifierRenderTarget->CreateBitmap(size, properties, &mMagnifierBitmap);
		needUpdate = true;
	}

	if (nullptr != mMagnifierBitmap && needUpdate)
	{
		if (auto rawDataPtr = mMagnifierBitmapUpdater.BeginRead())
		{
			auto magnifierData = reinterpret_cast<const MagnifierData*>(rawDataPtr);
			int stride = magnifierData->side * sizeof(COLORREF);
			auto buffer = magnifierData->buffer;

			mMagnifierBitmap->CopyFromMemory(nullptr, buffer, stride);
			mMagnifierBitmapNeedsUpdate = false;
			mMagnifierBitmapUpdater.EndRead(rawDataPtr);
		}
	}
}


void ImageKeeper::UpdateAnalyticsBitmap()
{
	if (!mShowAnalytic)
	{
		return;
	}

	if (auto ptr = mAnalyticsUpdater.BeginRead())
	{
		bool needsUpdate = mAnalyticsNeedsUpdate;
		if (nullptr == mAnalyticsBitmap && nullptr != mMagnifierBitmap)
		{
			D2D1_SIZE_U size = { kSideOfMagnifierSquare, kSideOfMagnifierSquare };
			D2D1_BITMAP_PROPERTIES properties = { mMagnifierBitmap->GetPixelFormat(), 0, 0 };
			mMagnifierRenderTarget->CreateBitmap(size, properties, &mAnalyticsBitmap);
			needsUpdate |= mIsAnalyticsBitmapValid;
		}
		if (mAnalyticsBitmap && needsUpdate)
		{
			auto analyticsData = reinterpret_cast<const AnalyticsData*>(ptr);
			static const int stride = kSideOfMagnifierSquare * sizeof(COLORREF);
			auto bufferPtr = analyticsData->bitmapData;

			D2D1_RECT_U rect = { 0, 0, kSideOfMagnifierSquare, kSideOfMagnifierSquare };
			HRESULT hr = mAnalyticsBitmap->CopyFromMemory(nullptr, bufferPtr, stride);
			mAnalyticsNeedsUpdate = false;
			mIsAnalyticsBitmapValid = true;
		}
		mAnalyticsUpdater.EndRead(ptr);
	}
}


void ImageKeeper::UpdateCircleBitmap()
{
	if (auto data = reinterpret_cast<const CircleData*>(mCircleUpdater.BeginRead()))
	{
		bool needsUpdate = mCircleNeedsUpdate;
		if (nullptr == mCircleBitmap && nullptr != mMagnifierBitmap)
		{
			D2D1_SIZE_U size = { kSideOfMagnifierSquare, kSideOfMagnifierSquare };
			D2D1_BITMAP_PROPERTIES properties = { mMagnifierBitmap->GetPixelFormat(), 0, 0 };
			mMagnifierRenderTarget->CreateBitmap(size, properties, &mCircleBitmap);
			needsUpdate |= mIsCircleBitmapValid;
		}
		if (mCircleBitmap && needsUpdate)
		{
			int stride = kSideOfMagnifierSquare * sizeof(COLORREF);
			auto bufferPtr = data->smoothData;

			HRESULT hr = mCircleBitmap->CopyFromMemory(nullptr, bufferPtr, stride);
			mCircleNeedsUpdate = false;
			mIsCircleBitmapValid = SUCCEEDED(hr);
		}
		// --
		if (nullptr == mSmoothStripeBitmap && nullptr != mMagnifierBitmap)
		{
			D2D1_SIZE_U size = { kSideOfMagnifierSquare, kHeightOfSmoothStripe };
			D2D1_BITMAP_PROPERTIES properties = { mMagnifierBitmap->GetPixelFormat(), 0, 0 };
			mMagnifierRenderTarget->CreateBitmap(size, properties, &mSmoothStripeBitmap);
		}
		if (mSmoothStripeBitmap && needsUpdate)
		{
			// copy data to a buffer
			const COLORREF* data2pi = data->smoothStripeLine;
			std::vector<COLORREF> buffer(kSideOfMagnifierSquare * kHeightOfSmoothStripe);
			const int countOfLeadingBytes = kPositionOfZeroAngle * sizeof(COLORREF);
			::memcpy(&buffer[0], &data2pi[kStripeInterval2pi - kPositionOfZeroAngle],
				countOfLeadingBytes);

			::memcpy(&buffer[kPositionOfZeroAngle], data2pi, kStripeInterval2pi * sizeof(COLORREF));

			const int countOfTrailingBytes = 
				(kSideOfMagnifierSquare - kPositionOf2PiAngle) * sizeof(COLORREF);
			::memcpy(&buffer[kPositionOf2PiAngle], data2pi, countOfTrailingBytes);

			COLORREF* q = &buffer[0];
			const int stride = kSideOfMagnifierSquare * sizeof(COLORREF);
			for (int y = 1; y < kHeightOfSmoothStripe; ++y)
			{
				q += kSideOfMagnifierSquare;
				::memcpy(q, &buffer[0], stride);
			}

			mSmoothStripeBitmap->CopyFromMemory(nullptr, &buffer[0], stride);
		}
		mCircleDivider = data->circleDivider;
		mCircleDividerFew = data->circleDividerFew;
		mCircleUpdater.EndRead(data);
	}
}
